package cc.aileron.accessor.properties;

import static cc.aileron.accessor.PojoAccessor.Type.GET;
import static cc.aileron.accessor.PojoAccessor.Type.SET;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;

import cc.aileron.accessor.PojoAccessor;
import cc.aileron.accessor.PojoAccessorNotFoundError;

/**
 * PojoProperties 実態
 * 
 * @author Aileron
 * 
 * @param <T>
 */
class PojoPropertiesImpl<T> implements PojoProperties<T>
{
    private static final String setterPattern = "set";
    private static final String[] getterPatterns = { "get", "is", "to" };

    public Object get(final T target, final String name)
    {
        final String key = name.toLowerCase();
        for (final String pattern : getterPatterns)
        {
            final String methodName = pattern + key;
            final Method method = methods.get(methodName);
            if (method != null)
            {
                try
                {
                    return method.invoke(target);
                }
                catch (final Exception e)
                {
                    throw new Error(e);
                }
            }
        }
        final Field field = fields.get(key);
        if (field != null)
        {
            try
            {
                return field.get(target);
            }
            catch (final Exception e)
            {
                throw new Error(e);
            }
        }
        throw new PojoAccessorNotFoundError(this.targetClass, name, GET);
    }

    public void set(final T target, final String name, final Object value)
    {
        final String key = name.toLowerCase();
        final String methodName = setterPattern + key;
        final Method method = methods.get(methodName);
        if (method != null)
        {
            try
            {
                method.invoke(target,
                        value);
                return;
            }
            catch (final Exception e)
            {
                throw new Error(e);
            }
        }
        final Field field = fields.get(key);
        if (field != null)
        {
            try
            {
                field.set(target,
                        value);
                return;
            }
            catch (final Exception e)
            {
                throw new Error(e);
            }
        }
        throw new PojoAccessorNotFoundError(targetClass, name, SET);
    }

    public Class<?> type(final PojoAccessor.Type type, final String name)
    {
        switch (type)
        {
        case GET:
            return typeGetter(name);
        case SET:
            return typeSetter(name);
        default:
            return null;
        }
    }

    /**
     * @param name
     * @return type
     */
    private Class<?> typeGetter(final String name)
    {
        final String key = name.toLowerCase();
        for (final String pattern : getterPatterns)
        {
            final Method method = methods.get(pattern + key);
            if (method != null)
            {
                return method.getReturnType();
            }
        }
        final Field field = fields.get(key);
        if (field != null)
        {
            return field.getType();
        }
        throw new PojoAccessorNotFoundError(targetClass, name, GET);
    }

    /**
     * @param name
     * @return type
     */
    private Class<?> typeSetter(final String name)
    {
        final String key = name.toLowerCase();
        final Method method = methods.get(setterPattern + key);
        if (method != null)
        {
            return method.getParameterTypes()[0];
        }
        final Field field = fields.get(key);
        if (field != null)
        {
            return field.getType();
        }
        throw new PojoAccessorNotFoundError(targetClass, name, SET);
    }

    /**
     * constractor
     * 
     * @param target
     */
    public PojoPropertiesImpl(final Class<T> target)
    {
        this.targetClass = target;
        for (final Method method : target.getMethods())
        {
            if (!method.isAccessible())
            {
                try
                {
                    method.setAccessible(true);
                }
                catch (final SecurityException e)
                {
                    continue;
                }
            }
            methods.put(method.getName()
                .toLowerCase(),
                    method);
        }
        for (final Field field : target.getFields())
        {
            if (!field.isAccessible())
            {
                try
                {
                    field.setAccessible(true);
                }
                catch (final SecurityException e)
                {
                    continue;
                }
            }
            fields.put(field.getName()
                .toLowerCase(),
                    field);
        }
    }

    /**
     * 対象のクラス
     */
    private final Class<T> targetClass;

    /**
     * メソッドの一覧
     */
    private final HashMap<String, Method> methods = new HashMap<String, Method>();

    /**
     * フィールドの一覧
     */
    private final HashMap<String, Field> fields = new HashMap<String, Field>();
}